<!DOCTYPE html>
<html>
    
<head>
    <meta charset="utf-8">
    
    <title>Handler 机制 | ApocalypseBlog</title>
    <canvas id="header_canvas"style="position:absolute;bottom:0"></canvas> 
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">

    

    

    

    

    
<link rel="stylesheet" href="/dist/build.css?v=1.14.0.css">

    <script src="/javascripts/bubble.js"></script>
    <script>
        window.isPost = true
        window.aomori = {
            
            
        }
        window.aomori_logo_typed_animated = false
        window.aomori_search_algolia = false

    </script>
<script>
    ((window.gitter = {}).chat = {}).options = {
      room: 'ApocalypseBlog/Apocalypse'
    };
  </script>
  <script src="https://sidecar.gitter.im/dist/sidecar.v1.js" async defer></script>
<meta name="generator" content="Hexo 5.3.0"></head>
<!--DaoVoice服务http://blog.daovoice.io/-->

    <script>(function (i, s, o, g, r, a, m) {
        i['DaoVoiceObject'] = r;
        i[r] = i[r] ||
          function () {
            (i[r].q = i[r].q || []).push(arguments);
          };
        i[r].l = 1 * new Date();
        a = s.createElement(o);
        m = s.getElementsByTagName(o)[0];
        a.async = 1;
        a.src = g;
        a.charset = 'utf-8';
        m.parentNode.insertBefore(a, m);
      })(window, document, 'script', ('https:' === document.location.protocol ? 'https:' : 'http:') + "//widget.daovoice.io/widget/b00f5052.js", 'daovoice');
      daovoice('init', {
        app_id: "b00f5052",
      });
      daovoice('update');
    </script>
  
<body>

    <div class="container">
    <header class="header">
        <div class="header-type">
            
            <div class="header-type-inner">
                
                    <a class="header-type-title" href="/">ApocalypseBlog</a>
                
    
                
            </div>
        </div>
        <div class="header-menu">
            <div class="header-menu-inner">
                
            </div>
            <div class="header-menu-social">
                
            </div>
        </div>

        <div class="header-menu-mobile">
            <div class="header-menu-mobile-inner" id="mobile-menu-open">
                <i class="icon icon-menu"></i>
            </div>
        </div>
    </header>

    <div class="header-menu-mobile-menu">
        <div class="header-menu-mobile-menu-bg"></div>
        <div class="header-menu-mobile-menu-wrap">
            <div class="header-menu-mobile-menu-inner">
                <div class="header-menu-mobile-menu-close" id="mobile-menu-close">
                    <i class="icon icon-cross"></i>
                </div>
                <div class="header-menu-mobile-menu-list">
                    
                </div>
            </div>
        </div>
    </div>

</div>

    <div class="container">
        <div class="main">
            <section class="inner">
                <section class="inner-main">
                    <div class="post">
    <article id="post-ckk6o6auj00057knygtj199f7" class="article article-type-post" itemscope
    itemprop="blogPost">

    <div class="article-inner">

        
          
        
        
        

        
        <header class="article-header">
            
  
    <h1 class="article-title" itemprop="name">
      Handler 机制
    </h1>
  

        </header>
        

        <div class="article-more-info article-more-info-post hairline">

            <div class="article-date">
  <time datetime="2020-06-09T02:19:36.000Z" itemprop="datePublished">2020-06-09</time>
</div>

            

            

            

        </div>

        <div class="article-entry post-inner-html hairline" itemprop="articleBody">
            <h1 id="面试官：“看你简历上写熟悉-Handler-机制，那聊聊-IdleHandler-吧？”"><a href="#面试官：“看你简历上写熟悉-Handler-机制，那聊聊-IdleHandler-吧？”" class="headerlink" title="面试官：“看你简历上写熟悉 Handler 机制，那聊聊 IdleHandler 吧？”"></a>面试官：“看你简历上写熟悉 Handler 机制，那聊聊 IdleHandler 吧？”</h1><a id="more"></a>

<h2 id="一-序"><a href="#一-序" class="headerlink" title="一. 序"></a>一. 序</h2><p>Handler 机制算是 Android 基本功，面试常客。但现在面试，多数已经不会直接让你讲讲 Handler 的机制，Looper 是如何循环的，MessageQueue 是如何管理 Message 等，而是基于场景去提问，看看你对 Handler 机制的掌握是否扎实。</p>
<p>本文就来聊聊 Handler 中的 IdleHandler，这个我们比较少用的功能。它能干什么？怎么使用？有什么合适的使用场景？哪些不是合适的使用场景？在 Android Framework 中有哪些地方用到了它？</p>
<h2 id="二-IdleHandler"><a href="#二-IdleHandler" class="headerlink" title="二. IdleHandler"></a>二. IdleHandler</h2><h3 id="2-1-简单说说-Handler-机制"><a href="#2-1-简单说说-Handler-机制" class="headerlink" title="2.1 简单说说 Handler 机制"></a>2.1 简单说说 Handler 机制</h3><p>在说 IdleHandler 之前，先简单了解一下 Handler 机制。</p>
<p>Handler 是标准的事件驱动模型，存在一个消息队列 MessageQueue，它是一个基于消息触发时间的优先级队列，还有一个基于此消息队列的事件循环 Looper，Looper 通过循环，不断的从 MessageQueue 中取出待处理的 Message，再交由对应的事件处理器 Handler/callback 来处理。</p>
<p>其中 MessageQueue 被 Looper 管理，Looper 在构造时同步会创建 MessageQueue，并利用 ThreadLocal 这种 TLS，将其与当前线程绑定。而 App 的主线程在启动时，已经构造并准备好主线程的 Looper 对象，开发者只需要直接使用即可。</p>
<p>Handler 类中封装了大部分「Handler 机制」对外的操作接口，可以通过它的 send/post 相关的方法，向消息队列 MessageQueue 中插入一条 Message。在 Looper 循环中，又会不断的从 MessageQueue 取出下一条待处理的 Message 进行处理。</p>
<p>IdleHandler 使用相关的逻辑，就在 MessageQueue 取消息的 <code>next()</code> 方法中。</p>
<h3 id="2-2-IdleHandler-是什么？怎么用？"><a href="#2-2-IdleHandler-是什么？怎么用？" class="headerlink" title="2.2 IdleHandler 是什么？怎么用？"></a>2.2 IdleHandler 是什么？怎么用？</h3><p><strong>IdleHandler 说白了，就是 Handler 机制提供的一种，可以在 Looper 事件循环的过程中，当出现空闲的时候，允许我们执行任务的一种机制。</strong></p>
<p>IdleHandler 被定义在 MessageQueue 中，它是一个接口。</p>
<pre><code>// MessageQueue.java
public static interface IdleHandler &#123;
  boolean queueIdle();
&#125;
</code></pre>
<p>可以看到，定义时需要实现其 <code>queueIdle()</code> 方法。同时返回值为 true 表示是一个持久的 IdleHandler 会重复使用，返回 false 表示是一个一次性的 IdleHandler。</p>
<p>既然 IdleHandler 被定义在 MessageQueue 中，使用它也需要借助 MessageQueue。在 MessageQueue 中定义了对应的 add 和 remove 方法。</p>
<pre><code>// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) &#123;
    // ...
  synchronized (this) &#123;
    mIdleHandlers.add(handler);
  &#125;
&#125;
public void removeIdleHandler(@NonNull IdleHandler handler) &#123;
  synchronized (this) &#123;
    mIdleHandlers.remove(handler);
  &#125;
&#125;
</code></pre>
<p>可以看到 add 或 remove 其实操作的都是 <code>mIdleHandlers</code>，它的类型是一个 ArrayList。</p>
<p><strong>既然 IdleHandler 主要是在 MessageQueue 出现空闲的时候被执行，那么何时出现空闲？</strong></p>
<p>MessageQueue 是一个基于消息触发时间的优先级队列，所以队列出现空闲存在两种场景。</p>
<ol>
<li>MessageQueue 为空，没有消息；</li>
<li>MessageQueue 中最近需要处理的消息，是一个延迟消息（when&gt;currentTime），需要滞后执行；</li>
</ol>
<p>这两个场景，都会尝试执行 IdleHandler。</p>
<p>处理 IdleHandler 的场景，就在 <code>Message.next()</code> 这个获取消息队列下一个待执行消息的方法中，我们跟一下具体的逻辑。</p>
<pre><code>Message next() &#123;
    // ...
  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) &#123;
    nativePollOnce(ptr, nextPollTimeoutMillis);

    synchronized (this) &#123;
      // ...
      if (msg != null) &#123;
        if (now &lt; msg.when) &#123;
          // 计算休眠的时间
          nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        &#125; else &#123;
          // Other code
          // 找到消息处理后返回
          return msg;
        &#125;
      &#125; else &#123;
        // 没有更多的消息
        nextPollTimeoutMillis = -1;
      &#125;

      if (pendingIdleHandlerCount &lt; 0
          &amp;&amp; (mMessages == null || now &lt; mMessages.when)) &#123;
        pendingIdleHandlerCount = mIdleHandlers.size();
      &#125;
      if (pendingIdleHandlerCount &lt;= 0) &#123;
        mBlocked = true;
        continue;
      &#125;

      if (mPendingIdleHandlers == null) &#123;
        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
      &#125;
      mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
    &#125;

    for (int i = 0; i &lt; pendingIdleHandlerCount; i++) &#123;
      final IdleHandler idler = mPendingIdleHandlers[i];
      mPendingIdleHandlers[i] = null; 

      boolean keep = false;
      try &#123;
        keep = idler.queueIdle();
      &#125; catch (Throwable t) &#123;
        Log.wtf(TAG, &quot;IdleHandler threw exception&quot;, t);
      &#125;

      if (!keep) &#123;
        synchronized (this) &#123;
          mIdleHandlers.remove(idler);
        &#125;
      &#125;
    &#125;

    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0;
  &#125;
&#125;
</code></pre>
<p>我们先解释一下 <code>next()</code> 中关于 IdleHandler 执行的主逻辑：</p>
<ol>
<li>准备执行 IdleHandler 时，说明当前待执行的消息为 null，或者这条消息的执行时间未到；</li>
<li>当 <code>pendingIdleHandlerCount &lt; 0</code> 时，根据 <code>mIdleHandlers.size()</code> 赋值给 <code>pendingIdleHandlerCount</code>，它是后期循环的基础；</li>
<li>将 <code>mIdleHandlers</code> 中的 IdleHandler 拷贝到 <code>mPendingIdleHandlers</code> 数组中，这个数组是临时的，之后进入 for 循环；</li>
<li>循环中从数组中取出 IdleHandler，并调用其 <code>queueIdle()</code> 记录返回值存到 <code>keep</code> 中；</li>
<li>当 <code>keep</code> 为 false 时，从 <code>mIdleHandler</code> 中移除当前循环的 IdleHandler，反之则保留；</li>
</ol>
<p>可以看到 IdleHandler 机制中，最核心的就是在 <code>next()</code> 中，当队列空闲的时候，循环 mIdleHandler 中记录的 IdleHandler 对象，如果其 <code>queueIdle()</code> 返回值为 <code>false</code> 时，将其从 <code>mIdleHander</code> 中移除。</p>
<p>需要注意的是，对 <code>mIdleHandler</code> 这个 List 的所有操作，都通过 synchronized 来保证线程安全，这一点无需担心。</p>
<h3 id="2-3-IdleHander-是如何保证不进入死循环的？"><a href="#2-3-IdleHander-是如何保证不进入死循环的？" class="headerlink" title="2.3 IdleHander 是如何保证不进入死循环的？"></a>2.3 IdleHander 是如何保证不进入死循环的？</h3><p>当队列空闲时，会循环执行一遍 <code>mIdleHandlers</code> 数组并执行 <code>IdleHandler.queueIdle()</code> 方法。而如果数组中有一些 IdleHander 的 <code>queueIdle()</code> 返回了 <code>true</code>，则会保留在 <code>mIdleHanders</code> 数组中，下次依然会再执行一遍。</p>
<p>注意现在代码逻辑还在 <code>MessageQueue.next()</code> 的循环中，在这个场景下 IdleHandler 机制是如何保证不会进入死循环的？</p>
<p>有些文章会说 IdleHandler 不会死循环，是因为下次循环调用了 <code>nativePollOnce()</code> 借助 epoll 机制进入休眠状态，下次有新消息入队的时候会重新唤醒，但这是不对的。</p>
<p>注意看前面 <code>next()</code> 中的代码，在方法的末尾会重置 pendingIdleHandlerCount 和 nextPollTimeoutMillis。</p>
<pre><code>Message next() &#123;
    // ...
  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) &#123;
        nativePollOnce(ptr, nextPollTimeoutMillis);
    // ...
    // 循环执行 mIdleHandlers
    // ...
    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0;
  &#125;
&#125;
</code></pre>
<p>nextPollTimeoutMillis 决定了下次进入 <code>nativePollOnce()</code> 超时的时间，它传递 0 的时候等于不会进入休眠，所以说 <code>natievPollOnce()</code> 进入休眠所以不会死循环是不对的。</p>
<p>这很好理解，毕竟 <code>IdleHandler.queueIdle()</code> 运行在主线程，它执行的时间是不可控的，那么 MessageQueue 中的消息情况可能会变化，所以需要再处理一遍。</p>
<p>实际不会死循环的关键是在于 pendingIdleHandlerCount，我们看看下面的代码。</p>
<pre><code>Message next() &#123;
    // ...
  // Step 1
  int pendingIdleHandlerCount = -1; 
  int nextPollTimeoutMillis = 0;
  for (;;) &#123;
    nativePollOnce(ptr, nextPollTimeoutMillis);

    synchronized (this) &#123;
      // ...
      // Step 2
      if (pendingIdleHandlerCount &lt; 0
          &amp;&amp; (mMessages == null || now &lt; mMessages.when)) &#123;
        pendingIdleHandlerCount = mIdleHandlers.size();
      &#125;
         // Step 3
      if (pendingIdleHandlerCount &lt;= 0) &#123;
        mBlocked = true;
        continue;
      &#125;
      // ...
    &#125;
        // Step 4
    pendingIdleHandlerCount = 0;
    nextPollTimeoutMillis = 0;
  &#125;
&#125;
</code></pre>
<p>我们梳理一下：</p>
<ul>
<li>Step 1，循环开始前，<code>pendingIdleHandlerCount</code> 的初始值为 -1；</li>
<li>Step 2，在 <code>pendingIdleHandlerCount&lt;0</code> 时，才会通过 <code>mIdleHandlers.size()</code> 赋值。也就是说只有第一次循环才会改变 <code>pendingIdleHandlerCount</code> 的值；</li>
<li>Step 3，如果 <code>pendingIdleHandlerCount&lt;=0</code> 时，则循环 continus；</li>
<li>Step 4，重置 <code>pendingIdleHandlerCount</code> 为 0；</li>
</ul>
<p>在第二次循环时，<code>pendingIdleHandlerCount</code> 等于 0，在 Step 2 不会改变它的值，那么在 Step 3 中会直接 continus 继续下一次循环，此时没有机会修改 <code>nextPollTimeoutMillis</code>。</p>
<p>那么 <code>nextPollTimeoutMillis</code> 有两种可能：-1 或者下次唤醒的等待间隔时间，在执行到 <code>nativePollOnce()</code> 时就会进入休眠，等待再次被唤醒。</p>
<p>下次唤醒时，<code>mMessage</code> 必然会有一个待执行的 Message，则 <code>MessageQueue.next()</code> 返回到 <code>Looper.loop()</code> 的循环中，分发处理这个 Message，之后又是一轮新的 <code>next()</code> 中去循环。</p>
<h3 id="2-4-framework-中如何使用-IdleHander？"><a href="#2-4-framework-中如何使用-IdleHander？" class="headerlink" title="2.4 framework 中如何使用 IdleHander？"></a>2.4 framework 中如何使用 IdleHander？</h3><p>到这里基本上就讲清楚 IdleHandler 如何使用以及一些细节，接下来我们来看看，在系统中，有哪些地方会用到 IdleHandler 机制。</p>
<p>在 AS 中搜索一下 IdleHandler。</p>
<p><img src="http://gank.io/images/fb0ad74ad4df4ad4be358282fea3cd7b" alt="img"></p>
<p>简单解释一下：</p>
<ol>
<li>ActivityThread.Idler 在 <code>ActivityThread.handleResumeActivity()</code> 中调用。</li>
<li>ActivityThread.GcIdler 是在内存不足时，强行 GC；</li>
<li>Instrumentation.ActivityGoing 在 Activity onCreate() 执行前添加；</li>
<li>Instrumentation.Idler 调用的时机就比较多了，是键盘相关的调用；</li>
<li>TextToSpeechService.SynthThread 是在 TTS 合成完成之后发送广播；</li>
</ol>
<p>有兴趣可以自己追一下源码，这些都是使用的场景，具体用 IdleHander 干什么，还是要看业务。</p>
<h2 id="三-一些面试问题"><a href="#三-一些面试问题" class="headerlink" title="三.一些面试问题"></a>三.一些面试问题</h2><p>到这里我们就讲清楚 IdleHandler 干什么？怎么用？有什么问题？以及使用中一些原理的讲解。</p>
<p>下面准备一些基本的问题，供大家理解。</p>
<p><strong>Q：IdleHandler 有什么用？</strong></p>
<ol>
<li>IdleHandler 是 Handler 提供的一种在消息队列空闲时，执行任务的时机；</li>
<li>当 MessageQueue 当前没有立即需要处理的消息时，会执行 IdleHandler；</li>
</ol>
<p><strong>Q：MessageQueue 提供了 add/remove IdleHandler 的方法，是否需要成对使用？</strong></p>
<ol>
<li>不是必须；</li>
<li>IdleHandler.queueIdle() 的返回值，可以移除加入 MessageQueue 的 IdleHandler；</li>
</ol>
<p><strong>Q：当 mIdleHanders 一直不为空时，为什么不会进入死循环？</strong></p>
<ol>
<li>只有在 pendingIdleHandlerCount 为 -1 时，才会尝试执行 mIdleHander；</li>
<li>pendingIdlehanderCount 在 next() 中初始时为 -1，执行一遍后被置为 0，所以不会重复执行；</li>
</ol>
<p><strong>Q：是否可以将一些不重要的启动服务，搬移到 IdleHandler 中去处理？</strong></p>
<ol>
<li>不建议；</li>
<li>IdleHandler 的处理时机不可控，如果 MessageQueue 一直有待处理的消息，那么 IdleHander 的执行时机会很靠后；</li>
</ol>
<p><strong>Q：IdleHandler 的 queueIdle() 运行在那个线程？</strong></p>
<ol>
<li>陷进问题，queueIdle() 运行的线程，只和当前 MessageQueue 的 Looper 所在的线程有关；</li>
<li>子线程一样可以构造 Looper，并添加 IdleHandler；</li>
</ol>
<h2 id="三-小结时刻"><a href="#三-小结时刻" class="headerlink" title="三. 小结时刻"></a>三. 小结时刻</h2><p>到这里就把 IdleHandler 的使用和原理说清除了。</p>
<p>IdleHandler 是 Handler 提供的一种在消息队列空闲时，执行任务的时机。但它执行的时机依赖消息队列的情况，那么如果 MessageQueue 一直有待执行的消息时，IdleHandler 就一直得不到执行，也就是它的执行时机是不可控的，不适合执行一些对时机要求比较高的任务。</p>

        </div>

    </div>

    

    

    

    

    

    
<nav class="article-nav">
  
    <a href="/2020/06/11/%E4%BB%8E0%E5%88%B01%E6%95%99%E4%BD%A0%E6%90%AD%E5%BB%BA%E5%89%8D%E7%AB%AF%E5%9B%A2%E9%98%9F%E7%9A%84%E7%BB%84%E4%BB%B6%E7%B3%BB%E7%BB%9F%EF%BC%88%E9%AB%98%E7%BA%A7%E8%BF%9B%E9%98%B6%E5%BF%85%E5%A4%87%EF%BC%89/" id="article-nav-newer" class="article-nav-link-wrap">
      <div class="article-nav-caption">下一篇</div>
      <div class="article-nav-title">
        
          从0到1教你搭建前端团队的组件系统（高级进阶必备）
        
      </div>
    </a>
  
  
    <a href="/2020/06/08/%E3%80%90Flutter%E9%AB%98%E7%BA%A7%E7%8E%A9%E6%B3%95%E3%80%91%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF%E7%9A%84%E8%A1%A8%E8%B1%A1%E8%AE%A4%E7%9F%A5/" id="article-nav-older" class="article-nav-link-wrap">
      <div class="article-nav-caption">上一篇</div>
      <div class="article-nav-title">Flutter高级玩法-贝塞尔曲线的表象认知</div>
    </a>
  
</nav>


    <section class="share">
        <div class="share-title">分享</div>
        <a class="share-item" target="_blank"
            href="https://twitter.com/share?text=Handler 机制 - ApocalypseBlog&url=http://example.com/2020/06/09/Handler-%E6%9C%BA%E5%88%B6/">
            <box-icon type='logo' name='twitter'></box-icon>
        </a>
        <a class="share-item" target="_blank"
            href="https://www.facebook.com/sharer.php?title=Handler 机制 - ApocalypseBlog&u=http://example.com/2020/06/09/Handler-%E6%9C%BA%E5%88%B6/">
            <box-icon name='facebook-square' type='logo' ></box-icon>
        </a>
        <!-- <a class="share-item" target="_blank"
            href="https://service.weibo.com/share/share.php?title=Handler 机制 - ApocalypseBlog&url=http://example.com/2020/06/09/Handler-%E6%9C%BA%E5%88%B6/&pic=">
            <div class="n-icon n-icon-weibo"></div>
        </a> -->
    </section>

</article>









</div>
                </section>
            </section>

             
            <aside class="sidebar">
            
                
            </aside>
        </div>
    </div>

    <footer class="footer">
    <div class="footer-wave">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320"><path fill="#3c4859" fill-opacity="1" d="M0,160L60,181.3C120,203,240,245,360,240C480,235,600,181,720,186.7C840,192,960,256,1080,261.3C1200,267,1320,213,1380,186.7L1440,160L1440,320L1380,320C1320,320,1200,320,1080,320C960,320,840,320,720,320C600,320,480,320,360,320C240,320,120,320,60,320L0,320Z"></path></svg>
    </div>

    <div class="footer-wrap">
        <div class="footer-inner"> 
            ApocalypseBlog &copy; 2021<br>
            Powered By Hexo · Theme By <a href="https://github.com/lh1me/hexo-theme-aomori" target="_blank">Aomori</a>
        </div>
    </div>

</footer>




<script src="/dist/build.js?1.14.0.js"></script>


<script src="/dist/custom.js?1.14.0.js"></script>









</body>

</html>